#include <string.h>
#include <stdexcept>
#include <cstdlib>

#include "JSONParser.h"

NahidaProject::JSONParser::JSONParser() : m_idx(0){

}

NahidaProject::JSONParser::~JSONParser(){

}

void NahidaProject::JSONParser::LoadFile(const std::string & str){
    targetString = str;
    m_idx = 0;
}

void NahidaProject::JSONParser::SkipWhiteSpace(){
	while (targetString[m_idx] == ' ' || targetString[m_idx] == '\r' || targetString[m_idx] == '\n' || targetString[m_idx] == '\t') {
		m_idx++;
	}
}

char NahidaProject::JSONParser::GetNextToken(){
    SkipWhiteSpace();
	if (m_idx == targetString.size()) {
		throw std::logic_error("unexpected end of input");
	}

    return targetString[m_idx++];
}

NahidaProject::JSON NahidaProject::JSONParser::Parse(){
    char ch = GetNextToken();
    switch (ch)
    {
        case 'n':
            m_idx--;
            return ParseNull();
        case 't':
        case 'f':
            m_idx--;
            return ParseBool();
        case '-':
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
            m_idx--;
            return ParseNumber();
        case '"':
            return JSON(ParseString());
        case '[':
            return ParseArray();
        case '{':
            return ParseObject();
        default:
            break;
    }
    throw std::logic_error("unexpected character in parse json");
}

NahidaProject::JSON NahidaProject::JSONParser::ParseNull(){
    if (targetString.compare(m_idx, 4, "null") == 0)
    {
        m_idx += 4;
        return JSON();
    }
    throw std::logic_error("parse null error");
}

NahidaProject::JSON NahidaProject::JSONParser::ParseBool()
{
    if (targetString.compare(m_idx, 4, "true") == 0)
    {
        m_idx += 4;
        return JSON(true);
    }
    if (targetString.compare(m_idx, 5, "false") == 0)
    {
        m_idx += 5;
        return JSON(false);
    }
    throw std::logic_error("parse bool error");
}

NahidaProject::JSON NahidaProject::JSONParser::ParseNumber()
{
    size_t pos = m_idx;

    if (targetString[m_idx] == '-')
        m_idx++;

    // integer part
    if (targetString[m_idx] == '0')
    {
        m_idx++;
    }
    else if (InRange(targetString[m_idx], '1', '9'))
    {
        m_idx++;
        while (InRange(targetString[m_idx], '0', '9'))
        {
            m_idx++;
        }
    }
    else
    {
        throw std::logic_error("invalid character in number");
    }

    if (targetString[m_idx] != '.')
    {
        return JSON(std::atoi(targetString.c_str() + pos));
    }

    // decimal part
    m_idx++;
    if (!InRange(targetString[m_idx], '0', '9'))
    {
        throw std::logic_error("at least one digit required in fractional part");
    }
    while (InRange(targetString[m_idx], '0', '9'))
    {
        m_idx++;
    }
    return JSON(std::atof(targetString.c_str() + pos));
}

std::string NahidaProject::JSONParser::ParseString(){
    int pos = m_idx;
    while (true){
        if (m_idx == targetString.size())
        {
            throw std::logic_error("unexpected end of input in string");
        }

        char ch = targetString[m_idx++];
        if (ch == '"')
        {
            break;
        }

        // The usual case: non-escaped characters
        if (ch == '\\')
        {
            ch = targetString[m_idx++];
            switch (ch)
            {
                case 'b':
                case 't':
                case 'n':
                case 'f':
                case 'r':
                case '"':
                case '\\':
                    break;
                case 'u':
                    m_idx += 4;
                    break;
                default:
                    break;
            }
        }
    }
    return targetString.substr(pos, m_idx - pos - 1);
}

NahidaProject::JSON NahidaProject::JSONParser::ParseArray(){
    JSON arr(JSON::jsonArray);
    char ch = GetNextToken();
    if (ch == ']')
    {
        return arr;
    }
    m_idx--;
    while (true)
    {
        arr.Append(Parse());
        ch = GetNextToken();
        if (ch == ']')
        {
            break;
        }
        if (ch != ',')
        {
            throw std::logic_error("expected ',' in array");
        }
    }
    return arr;
}

NahidaProject::JSON NahidaProject::JSONParser::ParseObject(){
    JSON obj(JSON::jsonObject);
    char ch = GetNextToken();
    if (ch == '}')
    {
        return obj;
    }
    m_idx--;
    while (true)
    {
        ch = GetNextToken();
        if (ch != '"')
        {
            throw std::logic_error("expected '\"' in object");
        }
        std::string key = ParseString();
        ch = GetNextToken();
        if (ch != ':')
        {
            throw std::logic_error("expected ':' in object");
        }
        obj[key] = Parse();
        ch = GetNextToken();
        if (ch == '}')
        {
            break;
        }
        if (ch != ',')
        {
            throw std::logic_error("expected ',' in object");
        }
    }
    return obj;
}